#include "main.h"

map_t *maps = NULL;
int nummaps = 0, mapnlen = 0, curmap = -1, mapnw, mapzoom = 1;
uint32_t *maps_buf = NULL;

/**
 * Add a map to list
 * id: used by the engine (could be -1)
 * name: used by the engine (could be NULL)
 * tid: used by the engine (tile set id, could be -1)
 * tw: tile width
 * th: tile height
 * w: map width in tiles
 * h: map height in tiles
 * l: number of map layers (could be 0 and map_add_under() and map_add_above() used)
 */
int map_add(int id, char *name, int tid, int tw, int th, int w, int h, int l)
{
    int i, j;

    if(maps) {
        if(id != -1) {
            for(i = 0; i < nummaps; i++)
                if(maps[i].id == id) {
                    if(tw > maps[i].tw) maps[i].tw = tw;
                    if(th > maps[i].th) maps[i].th = th;
                    return i;
                }
        } else
        if(name && *name)
            for(i = 0; i < nummaps; i++)
                if(!strcmp(maps[i].name, name)) {
                    if(tw > maps[i].tw) maps[i].tw = tw;
                    if(th > maps[i].th) maps[i].th = th;
                    return i;
                }
    }
    if(w > 0 && h > 0 && l >= 0) {
        i = nummaps++;
        if((maps = (map_t*)realloc(maps, nummaps * sizeof(map_t)))) {
            engine_ram += sizeof(map_t);
            memset(&maps[i], 0, sizeof(map_t));
            maps[i].id = id;
            if(name && *name) {
                strncpy(maps[i].name, name, NAMELEN - 1);
                j = textlen(name);
                if(j > mapnlen) mapnlen = j;
            }
            maps[i].tid = tid;
            maps[i].tw = tw;
            maps[i].th = th;
            maps[i].w = w;
            maps[i].h = h;
            maps[i].l = l;
            if(l > 0) {
                if((maps[i].data = (int*)malloc(w * h * l * 4))) {
                    engine_ram += w * h * l * 4;
                    memset(maps[i].data, 0xff, w * h * l * 4);
                } else {
                    engine_features |= EF_NOMEM;
                    debug("memory allocation error, map data too big %u x %u x %u", w, h, l);
                    nummaps--;
                    return -1;
                }
            }
            engine_features |= EF_MAPS;
            return i;
        } else {
            engine_features |= EF_NOMEM;
            debug("memory allocation error, too many maps %u", nummaps);
            nummaps = 0;
        }
    }
    return -1;
}

/**
 * Add a new layer under the existing ones
 */
int *map_add_under(int map)
{
    if(map < 0 || map >= nummaps || !maps[map].w || !maps[map].h) return NULL;
    if(!(maps[map].data = (int*)realloc(maps[map].data, maps[map].w * maps[map].h * (maps[map].l + 1) * 4))) {
        engine_features |= EF_NOMEM;
        debug("memory allocation error, map #%u data too big %u x %u x %u", map, maps[map].w, maps[map].h, maps[map].l + 1);
        maps[map].l = 0;
        return NULL;
    }
    engine_ram += maps[map].w * maps[map].h * 4;
    if(maps[map].l) memmove(maps[map].data + maps[map].w * maps[map].h, maps[map].data, maps[map].w * maps[map].h * maps[map].l * 4);
    memset(maps[map].data, 0xff, maps[map].w * maps[map].h * 4);
    maps[map].l++;
    return maps[map].data;
}

/**
 * Add a new layer above the existing ones
 */
int *map_add_above(int map)
{
    if(map < 0 || map >= nummaps || !maps[map].w || !maps[map].h) return NULL;
    if(!(maps[map].data = (int*)realloc(maps[map].data, maps[map].w * maps[map].h * (maps[map].l + 1) * 4))) {
        engine_features |= EF_NOMEM;
        debug("memory allocation error, map #%u data too big %u x %u x %u", map, maps[map].w, maps[map].h, maps[map].l + 1);
        maps[map].l = 0;
        return NULL;
    }
    engine_ram += maps[map].w * maps[map].h * 4;
    maps[map].l++;
    memset(maps[map].data + maps[map].w * maps[map].h * (maps[map].l - 1), 0xff, maps[map].w * maps[map].h * 4);
    return maps[map].data + maps[map].w * maps[map].h * (maps[map].l - 1);
}

/**
 * Return the data pointer for a map layer
 */
int *map_data(int map, int l)
{
    if(map < 0 || map >= nummaps || l < 0 || l >= maps[map].l || !maps[map].data) return NULL;
    return maps[map].data + maps[map].w * maps[map].h * l;
}

/**
 * Add sprite to the map
 */
int map_sprite(int map, int sprite, int x, int y)
{
    int i;

    if(map < 0 || map >= nummaps || sprite < 0 || sprite >= numsprites) return 0;
    i = maps[map].numspr++;
    if(!(maps[map].spr = (map_sprite_t*)realloc(maps[map].spr, maps[map].numspr * sizeof(map_sprite_t)))) {
        engine_features |= EF_NOMEM;
        debug("memory allocation error, map #%u data many sprites %u", map, maps[map].numspr);
        maps[map].numspr = 0;
        return 0;
    }
    engine_ram += sizeof(map_sprite_t);
    memset(&maps[map].spr[i], 0, sizeof(map_sprite_t));
    maps[map].spr[i].sprite = sprite;
    maps[map].spr[i].x = x;
    maps[map].spr[i].y = y;
    return 1;
}

/**
 * Display a tile (should be called by engines' prefix_drawtile() function)
 */
void map_tile(int sheet, int x, int y, int w, int h, int dx, int dy)
{
    SDL_Surface *src, *dst;
    SDL_Rect srcrect, dstrect;

    if(!maps_buf || maprect.w < 1 || maprect.h < 1 || sheet < 0 || sheet >= numsheets || !sheets[sheet].data ||
        x < 0 || y < 0 || x >= sheets[sheet].w || y >= sheets[sheet].h ||
        dx < 0 || dx >= maprect.w || dy < 0 || dy >= maprect.h) return;
    if(x + w > sheets[sheet].w) w = sheets[sheet].w - x;
    if(y + h > sheets[sheet].h) h = sheets[sheet].h - y;
    if(dx + w > maprect.w) w = maprect.w - dx;
    if(dy + h > maprect.h) h = maprect.h - dy;
    if(w < 1 || h < 1) return;
    src = SDL_CreateRGBSurfaceFrom(sheets[sheet].data, sheets[sheet].w, sheets[sheet].h, 32, sheets[sheet].w * 4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    dst = SDL_CreateRGBSurfaceFrom(maps_buf, maprect.w, maprect.h, 32, maprect.w * 4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    srcrect.w = dstrect.w = w; srcrect.h = dstrect.h = h;
    srcrect.x = x; srcrect.y = y; dstrect.x = dx; dstrect.y = dy;
    SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_BLEND);
    SDL_SetSurfaceBlendMode(dst, SDL_BLENDMODE_BLEND);
    SDL_BlitScaled(src, &srcrect, dst, &dstrect);
    SDL_FreeSurface(src); SDL_FreeSurface(dst);
}

/**
 * Display shadow (should be called by engines' prefix_drawtile() function)
 */
void map_shadow(uint32_t color, int w, int h, int dx, int dy)
{
    SDL_Surface *src, *dst;
    SDL_Rect srcrect, dstrect;

    if(!maps_buf || maprect.w < 1 || maprect.h < 1 || dx < 0 || dx >= maprect.w || dy < 0 || dy >= maprect.h) return;
    if(dx + w > maprect.w) w = maprect.w - dx;
    if(dy + h > maprect.h) h = maprect.h - dy;
    if(w < 1 || h < 1) return;
    src = SDL_CreateRGBSurfaceFrom(&color, 1, 1, 32, 4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    dst = SDL_CreateRGBSurfaceFrom(maps_buf, maprect.w, maprect.h, 32, maprect.w * 4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    srcrect.w = srcrect.h = 1; dstrect.w = w; dstrect.h = h;
    srcrect.x = srcrect.y = 0; dstrect.x = dx; dstrect.y = dy;
    SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_BLEND);
    SDL_SetSurfaceBlendMode(dst, SDL_BLENDMODE_BLEND);
    SDL_BlitScaled(src, &srcrect, dst, &dstrect);
    SDL_FreeSurface(src); SDL_FreeSurface(dst);
}

/**
 * Free lists
 */
void maps_free(void)
{
    int i;
    if(maps) {
        for(i = 0; i < nummaps; i++) {
            if(maps[i].data) free(maps[i].data);
            if(maps[i].spr) free(maps[i].spr);
        }
        free(maps); maps = NULL;
    }
    nummaps = mapnlen = 0;
}

/**
 * Select current map to display
 */
void maps_select(int idx)
{
    int i;

    if(idx >= nummaps) idx = nummaps - 1;
    if(idx < 0) idx = 0;
    curmap = idx;
    if(scrcur > curmap * 18) scrcur = curmap * 18;
    if(scrcur < (curmap + 1) * 18 - screen_h + 4) scrcur = (curmap + 1) * 18 - screen_h + 4;
    if(map) { SDL_DestroyTexture(map); map = NULL; }
    maprect.x = maprect.y = 0;
    maprect.w = maps[curmap].w * maps[curmap].tw; maprect.h = maps[curmap].h * maps[curmap].th;
    if(maprect.w > 0 && maprect.h > 0 &&
        (map = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, maprect.w, maprect.h))) {
        SDL_SetTextureBlendMode(map, SDL_BLENDMODE_BLEND);
        SDL_SetTextureScaleMode(map, SDL_ScaleModeLinear);
        SDL_LockTexture(map, NULL, (void*)&maps_buf, &i);
        if(maps_buf) {
            for(i = 0; i < maprect.w * maprect.h; i++)
                maps_buf[i] = BG - 0x080808;
            engine_map(&maps[curmap]);
            if(maps[curmap].spr && maps[curmap].numspr && sprites)
                for(i = 0; i < maps[curmap].numspr; i++)
                    if(maps[curmap].spr[i].sprite >= 0 && maps[curmap].spr[i].sprite < numsprites)
                        sprites_draw(maps[curmap].spr[i].sprite, 0, maps_buf, maprect.w, maprect.h, maprect.w,
                            maps[curmap].spr[i].x - sprites[maps[curmap].spr[i].sprite].w / 2,
                            maps[curmap].spr[i].y - sprites[maps[curmap].spr[i].sprite].h, 1);
            maps_buf = NULL;
        }
        SDL_UnlockTexture(map);
    }
    refresh = 1;
}

/**
 * Get a safe file name
 */
void maps_safename(int map, char *name)
{
    char *s;
    if(maps[map].name[0]) {
        if((s = strrchr(maps[map].name, '/'))) s++; else
        if((s = strrchr(maps[map].name, '\\'))) s++;
        else s = maps[map].name;
        strncpy(name, s, NAMELEN - 5);
    } else
        sprintf(name, "map%05u", maps[map].id != -1 ? maps[map].id : map);
}

/**
 * Export current map to PNG
 */
void maps_png(void)
{
    char tmp[NAMELEN];
    int p, s, w, h;
    uint8_t *buf, *img;

    if(!map || curmap < 0 || curmap >= nummaps || !maps[curmap].w || !maps[curmap].h || !maps[curmap].tw || !maps[curmap].th ||
        !maps[curmap].data) return;
    SDL_LockTexture(map, NULL, (void*)&img, &p);
    if(img) {
        w = maps[curmap].w * maps[curmap].tw;
        h = maps[curmap].h * maps[curmap].th;
        if((buf = stbi_write_png_to_mem((uint8_t*)img, p, w, h, 4, &s))) {
            maps_safename(curmap, tmp); strcat(tmp, ".png");
            savedialog(NULL, tmp, buf, s);
            free(buf);
        }
    }
    SDL_UnlockTexture(map);
}

/**
 * Export current map in Tiled Mapeditor format
 */
void maps_tmx(void)
{
    int i, len, l, x, y;
    char *buf, *str, tmp[NAMELEN];

    if(curmap < 0 || curmap >= nummaps || !maps[curmap].w || !maps[curmap].h || !maps[curmap].l || !maps[curmap].data) return;
    len = 1024 + maps[curmap].l * (256 + (maps[curmap].w * 12 + 4) * maps[curmap].h);
    if(!(buf = (char*)malloc(len))) {
        engine_features |= EF_NOMEM;
        debug("unable to allocate memory for tmx (%lu bytes)", len);
        return;
    }
    str = buf + sprintf(buf, "<map version=\"1.5\" orientation=\"orthogonal\" renderorder=\"left-down\" compressionlevel=\"0\" "
        "width=\"%u\" height=\"%u\" tilewidth=\"%u\" tileheight=\"%u\" infinite=\"0\" nextlayerid=\"%u\" nextobjectid=\"1\">\r\n"
        " <properties>\r\n  <property name=\"title\" value=\"%s\"/>\r\n  <property name=\"tilesetId\" value=\"%d\"/>\r\n </properties>\r\n",
        maps[curmap].w, maps[curmap].h, maps[curmap].tw, maps[curmap].th, maps[curmap].l + 1, maps[curmap].name, maps[curmap].tid);
    for(l = i = 0; l < maps[curmap].l; l++) {
        str += sprintf(str, " <layer id=\"%u\" name=\"\" width=\"%u\" height=\"%u\">\r\n  <data encoding=\"csv\">\r\n",
            l + 1, maps[curmap].w, maps[curmap].h);
        for(y = 0; y < maps[curmap].h; y++) {
            for(x = 0; x < maps[curmap].w; x++, i++)
                str += sprintf(str, "%s%4u", x ? "," : "", maps[curmap].data[i]);
            str += sprintf(str, "%s\r\n", y < maps[curmap].h - 1 ? "," : "");
        }
        str += sprintf(str, "  </data>\r\n </layer>\r\n");
    }
    str += sprintf(str, "</map>\r\n");
    maps_safename(curmap, tmp); strcat(tmp, ".tmx");
    savedialog(NULL, tmp, (uint8_t*)buf, (uintptr_t)str - (uintptr_t)buf);
    free(buf);
}

/**
 * UI Controller
 */
void maps_ctrl(void)
{
    static int move = 0, ox, oy, px, py;

    switch(event.type) {
        case SDL_KEYDOWN:
            switch(event.key.keysym.sym) {
                case SDLK_UP: maps_select(curmap - 1); break;
                case SDLK_DOWN: maps_select(curmap + 1); break;
                case SDLK_HOME: maps_select(0); break;
                case SDLK_END: maps_select(nummaps - 1); break;
                case SDLK_PAGEUP: maps_select(curmap - (screen_h / 18 - 1)); break;
                case SDLK_PAGEDOWN: maps_select(curmap + (screen_h / 18 - 1)); break;
                case SDLK_RETURN: field = pressedfield = 0; break;
                case SDLK_SPACE: mapzoom ^= 1; break;
            }
        break;
        case SDL_KEYUP:
            switch(event.key.keysym.sym) {
                case SDLK_RETURN: field = pressedfield = 0; goto doexp;
            }
        break;
        case SDL_MOUSEBUTTONUP:
doexp:      if(field == 0 && pressedfield == 0) {
                maps_png();
            } else
            if(field == 1 && pressedfield == 1) {
                maps_tmx();
            }
            move = 0;
        break;
        case SDL_MOUSEBUTTONDOWN:
            if(field == 2) {
                mapzoom ^= 1; refresh = 1;
            } else
            if(mx >= screen_w - 12 - mapnw) {
                maps_select((scrcur + my - 38) / 18);
            } else
            if(!mapzoom && mx >= 16 && mx < screen_w - 12 - mapnw && my >= 36 && my < screen_h - 24) {
                ox = maprect.x; oy = maprect.y; px = mx; py = my;
                move = 1;
            }
        break;
        case SDL_MOUSEMOTION:
            if(move) {
                maprect.x = ox - (mx - px); maprect.y = oy - (my - py);
                pressedfield = -1;
            }
        break;
    }
}

/**
 * UI View
 */
void maps_view(void)
{
    char tmp[256];
    int i, tot = nummaps * 18 - (screen_h - 4);

    /* failsafes */
    if(!nummaps) {
        engine_features &= ~EF_MAPS;
        tab = TAB_INFO;
        refresh = 1;
        return;
    }
    if(curmap < 0) maps_select(0);
    if(curmap >= nummaps) maps_select(nummaps - 1);

    strcpy(tmp, lang[EXPORT]); strcat(tmp, " (PNG)");
    i = textlen(tmp) + 16;
    fields[0].x = 2; fields[0].y = screen_h - 23; fields[0].w = i; fields[0].h = 21;
    button(fields[0].x, fields[0].y, i, 21, pressedfield == 0, tmp);

    strcpy(tmp, lang[EXPORT]); strcat(tmp, " (TMX)");
    fields[1].x = i + 6; fields[1].y = screen_h - 23; fields[1].w = i; fields[1].h = 21;
    button(fields[1].x, fields[1].y, i, 21, pressedfield == 1, tmp);

    fields[2].x = 2 + 2 * (i + 4); fields[2].y = screen_h - 22; fields[2].w = 16; fields[2].h = 21;
    chkbox(fields[2].x, fields[2].y, mapzoom, "");
    if(maps[curmap].name[0]) {
        crop_w = screen_w - 12;
        text(fields[2].x + 20, screen_h - 21, FG, maps[curmap].name);
        crop_w = screen_w;
    }

    hscroll(screen_w - 10, 0, screen_h, scrcur, tot, pressedfield == 999);

    crop_w = screen_w - 12;
    crop_h = screen_h - 2;
    mapnw = mapnlen;
    if(mapnw > screen_w / 4) mapnw = screen_w / 4;
    for(i = scrcur / 18; i < nummaps && i * 18 - scrcur < screen_h; i++) {
        box(screen_w - 12 - mapnw, 3 + i * 18 - scrcur, mapnw, 16, i == curmap ? FG : BG - 0x080808);
        text(screen_w - 12 - mapnw, 3 + i * 18 - scrcur, i == curmap ? BG : FG, maps[i].name);
    }
}

